// hiv (c) 2014 Ricardo Cruz <ricardo.pdm.cruz@gmail.com>

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>

#define USE_SDL
#ifdef USE_SDL
#include <SDL.h>
#endif

#define WORLD 500

#define T0   1e4
#define HIV0 1e2
#define AB0  1e2

typedef enum Type {
	HIV, T, AB
} Type;

typedef struct Agent {
	Type type;
	int x, y;
	int status, life;  // used by T
} Agent;

typedef struct List {
	Agent *agent;
	struct List *prev, *next;
} List;

inline static List *prepend_node(List *list, Agent *agent)
{
	List *node = malloc(sizeof(List));
	node->agent = agent;
	node->prev = 0;
	node->next = list;
	if(list)
		list->prev = node;
	return node;
}

inline static List *remove_node(List *list, List *node, int free_agent)  // returns previous agent
{
	List *ret = list;
	if(node->next)
		node->next->prev = node->prev;
	if(node->prev)
		node->prev->next = node->next;
	else
		ret = node->next;
	if(free_agent)
		free(node->agent);
	free(node);
	return ret;
}

static List *patch[WORLD][WORLD] = {{0}};

inline static Agent *new_agent(Type type, int x, int y)
{
	Agent *agent = malloc(sizeof(Agent));
	agent->type = type;
	agent->x = x;
	agent->y = y;
	agent->status = agent->life = 0;
	patch[y][x] = prepend_node(patch[y][x], agent);
	return agent;
}

inline static void new_agent_rand(Type type)
{
	new_agent(type, rand()%WORLD, rand()%WORLD);
}

static void setup()
{
	int i;
	for(i = 0; i < T0; i++)
		new_agent_rand(T);
	for(i = 0; i < HIV0; i++)
		new_agent_rand(HIV);
	for(i = 0; i < AB0; i++)
		new_agent_rand(AB);
}

static void interact()
{
	int x, y;
	List *l1, *l2;
	for(y = 0; y < WORLD; y++)
		for(x = 0; x < WORLD; x++)
			for(l1 = patch[y][x]; l1;) {
				int die1 = 0;
				for(l2 = patch[y][x]; l2 && !die1;) {
					int die2 = 0;
					Agent *a = l1->agent;
					Agent *b = l2->agent;
					switch(a->type) {
						case HIV:
							if(b->type == T && b->status == 0)
								if(rand() % 100 == 0) {  // infects: 1 in every 100 contacts
									b->life = rand()%2 ? 1e2 : 1e4;
									b->status = 1;
									die1 = 1;
								}
							break;
						case AB:
							switch(b->type) {
								case T:
									if(b->status == 1) {
//										if(rand()%2)
//										int i;
//										for(i = 0; i < 4; i++)
											new_agent(AB, a->x, a->y);
									}
									break;
								case HIV:
									die1 = die2 = 1;
									break;
								case AB: break;
							}
							break;
						case T:
							break;
					}
					if(die2) {
						List *next = l2->next;
						patch[y][x] = remove_node(patch[y][x], l2, 1);
						l2 = next;
					}
					else
						l2 = l2->next;
				}
				if(die1) {
					List *next = l1->next;
					patch[y][x] = remove_node(patch[y][x], l1, 1);
					l1 = next;
				}
				else
					l1 = l1->next;
			}
}

static void infection()
{
	int x, y;
	List *l;
	for(y = 0; y < WORLD; y++)
		for(x = 0; x < WORLD; x++)
			for(l = patch[y][x]; l;) {
				int die1 = 0;
				Agent *a = l->agent;
				if(a->type == T && a->life) {
					a->life--;
					if(!a->life) {
						int i;
						for(i = 0; i < 20; i++)
							new_agent(HIV, a->x, a->y);
						die1 = 1;
					}
				}
				if(die1) {
					List *next = l->next;
					patch[y][x] = remove_node(patch[y][x], l, 1);
					l = next;
				}
				else
					l = l->next;
			}
}

static void diffuse()
{
	int x, y;
	List *l;
	for(y = 0; y < WORLD; y++)
		for(x = 0; x < WORLD; x++)
			for(l = patch[y][x]; l;) {
				List *next = l->next;
				Agent *a = l->agent;
				patch[a->y][a->x] = remove_node(patch[a->y][a->x], l, 0);
				a->x += (rand()%3)-1;
				a->y += (rand()%3)-1;
				a->x = a->x < 0 ? a->x+WORLD : a->x%WORLD;
				a->y = a->y < 0 ? a->y+WORLD : a->y%WORLD;
				patch[a->y][a->x] = prepend_node(patch[a->y][a->x], a);
				l = next;
			}
}

static void print_line(FILE *csv)
{
	int count[3] = {0};
	int x, y;
	List *l;
	for(y = 0; y < WORLD; y++)
		for(x = 0; x < WORLD; x++)
			for(l = patch[y][x]; l; l = l->next) {
				Agent *a = l->agent;
				count[a->type]++;
			}
	fprintf(csv, "%d , %d , %d\n", count[0], count[1], count[2]);
}

#ifdef USE_SDL
static SDL_Surface *screen = 0;

static void init_graphics()
{
	SDL_Init(SDL_INIT_VIDEO);
	screen = SDL_SetVideoMode(WORLD, WORLD, 32, SDL_HWSURFACE);
	SDL_WM_SetCaption("HIV", NULL);
}

static void draw_agents()
{
	static int color[3] = {0xff0000, 0x005500, 0xffff00};
	SDL_FillRect(screen, NULL, 0);
	int x, y;
	List *l;
	for(y = 0; y < WORLD; y++)
		for(x = 0; x < WORLD; x++)
			for(l = patch[y][x]; l; l = l->next) {
				Agent *a = l->agent;
				int c = color[a->type];
				if(a->type == T && a->status)
					c = 0x7cff7c;
				SDL_Rect rect = {a->x, a->y, 2, 2};
				SDL_FillRect(screen, &rect, c);
			}
	SDL_Flip(screen);
}
#endif

void sigint() {
  exit(0);
}

int main(int argc, char *argv[])
{
	int graphics = argc > 1 && !strcmp(argv[1], "--visual");
	signal(SIGINT, sigint);
	srand(time(NULL));
	FILE *csv = fopen("hiv.csv", "w");
	fprintf(csv, "V , T , ab\n");
	setup();
	if(graphics)
		init_graphics();
	int t;
	for(t = 0; t < 2e4; t++) {
		if(t % 1000 == 0)
			printf("progress - %d%%\n", (int)(t/2e4*100));
		print_line(csv);
		interact();
		infection();
		diffuse();
		if(graphics && t % 5 == 0) {
			draw_agents();
			char str[32];
			snprintf(str, 32, "HIV - Ticks: %4d - %2d%%", t, (int)(t/2e4*100));
			SDL_WM_SetCaption(str, NULL);
		}
	}	
	fclose(csv);
	printf("It took %d minutes\n", SDL_GetTicks()/60000);
	return 0;
}

